function Jaiku(username, key){
	this.username   = username;
	this.key        = key;
	this.maxRetries = 3;
	this.cache      = { user: {} };
	this.log = function(){};
	this.error = function(){};
}

Jaiku.VERSION = function(){ return 1.2 };

// ================================================================= //
// Feed Access
// ================================================================= //

Jaiku.prototype.getMyFeed = function(callback){
	this.getFeed( callback, this.username );
};

Jaiku.prototype.getContactsFeed = function(callback){
	this.getFeed( callback, this.username, true );
};

Jaiku.prototype.getUserFeed = function(user, callback){
	this.getFeed( callback, user || this.username );
};

Jaiku.prototype.getFeed = function(callback, user, contacts, retries){
	callback = callback || function(){};
	retries  = retries  || 0;

	var urlUser     = (user)     ? user + '.' : '';
	var urlContacts = (contacts) ? 'contacts/' : '';

	var _this = this;

	Jaiku.get(
		'http://'+urlUser+'jaiku.com/'+urlContacts+'feed/json',
		{user: this.username, personal_key: this.key},
		function(rv){ _this.getFeedCallback(rv, callback, user, contacts, retries) }
	);
};
Jaiku.prototype.getFeedCallback = function(rt, callback, user, contacts, retries){
	try {
		var rv = Jaiku.parseJSON(rt);
	} catch(e) {
		if (rt){
			this.error("Data from  Jaiku.com is not valid JSON. Unable to get feed.");
			return;
		}
		if (retries < this.maxRetries) {
			retries++;
			this.log("Unable to get "+user+" feed. Retrying  #"+retries+".");
			this.getFeed(callback, user, contacts, retries);
		} else {
			this.log("Unable to get "+user+" feed. Giving up for this refresh.");
		}
		return;
	}
	for(var i = 0; i < rv.stream.length; i++){
		rv.stream[i].created_at = Date.from_ISOString( rv.stream[i].created_at );
	}
	callback(rv);
};


// ================================================================= //
// Presence Access
// ================================================================= //

Jaiku.prototype.getMyPresence = function(callback){
	this.getUserPresence(this.username, callback);
};

Jaiku.prototype.getUserPresence = function(user, callback, retries){
	user     = user     || this.username;
	callback = callback || function(){};
	retries  = retries  || 0;

	var _this = this;

	var urlUser = (user) ? user + '.' : '';

	Jaiku.get(
		'http://'+urlUser+'jaiku.com/presence/json',
		{user: this.username, personal_key: this.key},
		function(rv){ _this.getUserPresenceCallback(rv, user, callback, retries) }
	);
};
Jaiku.prototype.getUserPresenceCallback = function(rt, user, callback, retries){
	try {
		var rv = Jaiku.parseJSON(rt);
	} catch(e) {
		if (retries < this.maxRetries) {
			retries++;
			this.log("Unable to get presence for"+user+". Retrying  #"+retries+".");
			this.getUserPresence(user, callback, retries);
		} else {
			this.log("Unable to get presence for"+user+". Giving up for this refresh.");
		}
		return;
	}
	callback(rv);
};


// ================================================================= //
// User Info Access
// ================================================================= //

Jaiku.prototype.getMyInfo = function(callback){
	this.getUserInfo( this.username, callback );
};

Jaiku.prototype.getUserInfo = function(user, callback, retries){
	callback = callback || function(){};
	user     = user     || this.username;
	retries  = retries  || 0;

	if (this.cache.user[ user ]) {
		callback( this.cache.user[ user ] )
		return;
	}

	var _this = this;

	Jaiku.get(
		'http://'+user+'.jaiku.com/json',
		{user: this.username, personal_key: this.key},
		function(rv){ _this.getUserCallback(rv, user, callback, retries) }
	);
};
Jaiku.prototype.getUserCallback = function(rt, user, callback, retries){
	try {
		if (! /^\{/.test(rt)) { // Bug in early API
			rt = rt.replace(/^"user":\s*/,'').replace(/;\s*$/,'');
		}
		var rv = Jaiku.parseJSON(rt);
	} catch(e) {
		if (retries < this.maxRetries) {
			retries++;
			this.log(e);
			this.log("Unable to get user. Retrying  #"+retries+".");
			this.getUserInfo(user, callback, retries);
		} else {
			this.log("Unable to get user. Giving up for this refresh.");
		}
		return;
	}
	this.cache.user[ user ] = rv;
	callback(rv);
};


Jaiku.prototype.getMyChannels = function(callback){
	this.getUserChannels( this.username, callback );
};

Jaiku.prototype.getUserChannels = function(user, callback, retries){
	callback = callback || function(){};
	user     = user     || this.username;
	return this.getUserInfo( user,
		function(info){
			var channelList = [];
			if( info.contacts )
				for(var i = 0; i < info.contacts.length; i++)
					if ( /^http:\/\/jaiku.com\/channel\//.test( info.contacts[i].url ) )
						channelList.push(info.contacts[i]);
			callback(channelList)
		}
	)
};

// ================================================================= //
// Set the user status
// ================================================================= //

Jaiku.prototype.setPresence = function( newPresence,  callback ){
	if((! newPresence) || (newPresence instanceof Object && ! newPresence.hasOwnProperty('message'))){
		this.error("No presence given.");
		return;
	}
	callback = callback || function(){};

	var args = {
		user: this.username,
		personal_key: this.key,
		method: 'presence.send'
	};
	if (newPresence instanceof Object){
		for(var i in newPresence) if (newPresence.hasOwnProperty(i))
			args[i] = newPresence[i];
	} else {
		args.message = newPresence;
	}


	var _this = this;
	Jaiku.post(
		'http://api.jaiku.com/json',
		args,
		function(rv){ _this.setPresenceCallback(rv, newPresence, callback) }
	);
};
Jaiku.prototype.setPresenceCallback = function(rt, newPresence, callback){
	try {
		var rv = Jaiku.parseJSON(rt);
	} catch(e) {
		this.log("Unable to set presence. Will not retry.");
		return;
	}
	callback(rv['status'] ? rv['status'].toUpperCase() : 'NOT OK');
};


// ================================================================= //
// Utility functions
// ================================================================= //

Jaiku.get_relative_time = function(time_value) {
	var parsed_date = Date.parse(time_value);

	var relative_to = (arguments.length > 1) ? arguments[1] : new Date();
	var delta = parseInt((relative_to.getTime() - parsed_date) / 1000);

	if(delta < 60) {
		return 'less than a minute ago';
	} else if(delta < 120) {
		return 'about a minute ago';
	} else if(delta < (45*60)) {
		return (parseInt(delta / 60)).toString() + ' minutes ago';
	} else if(delta < (120*60)) {
		return 'about an hour ago';
	} else if(delta < (24*60*60)) {
		return 'about ' + (parseInt(delta / 3600)).toString() + ' hours ago';
	} else if(delta < (48*60*60)) {
		return '1 day ago';
	}
	return (parseInt(delta / 86400)).toString() + ' days ago';
};

//if(! Date.from_ISOString instanceof Function){
	Date.from_ISOString = function(str){
		var rv = new Date(0,0,0,0,0,0,0);
		var date = str.match(/(\d{4})\D*(?:(\d{2})\D*(?:(\d{2})\D*(?:(\d{2})\D*(?:(\d{2})\D*(?:(\d{2})\D*(?:(\d{2}))?)?)?)?)?)?/).slice(1);
		if(date[0]) rv.setUTCFullYear(date[0]);
		if(date[1]) rv.setUTCMonth(date[1]);
		if(date[2]) rv.setUTCDate(date[2]);

		if(date[3]) rv.setUTCHours(date[3]);
		if(date[4]) rv.setUTCMinutes(date[4]);
		if(date[5]) rv.setUTCSeconds(date[5]);
		if(date[6]) rv.setUTCMilliseconds(date[6]*1000);
		return rv;
	};
//}

Jaiku.parseJSON = function(json){
	if (/^[\],:{}\s]*$/.test(json.replace(/\\./g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){
		return eval('('+json+')');
	}
	throw("Source is not a valid JSON string");
}


Jaiku.iconsByCategory = {
	Transport:
		[301, 304, 316, 317, 358, 372, 375, 378, 379, 401],
	Weaponry:
		[308, 310, 343],
	'Household items':
		[302, 324, 326, 323, 327, 348, 349, 353, 354, 373, 377, 380, 383, 384, 386, 389, 394, 396, 397],
	'Audio Visual':
		[303, 305, 312, 314, 320, 328, 329, 330, 331, 342, 346, 336, 351, 352, 376],
	Clothing:
		[306, 311, 335, 340, 344, 345, 391, 325],
	'Food and beverages':
		[315, 319, 322, 334, 341, 360, 366, 369, 381, 385, 390],
	Sport:
		[307, 321, 339, 359, 362, 364, 387, 388],
	Activities:
		[313, 382, 395, 399],
	Tools:
		[309, 333, 332, 350, 368],
	'Generic icons':
		[337, 356],
	Symbols:
		[338, 357, 363, 367, 347, 392],
	Weather:
		[365, 370, 374, 398, 400],
	Misc:
		[318, 355, 361, 371, 393, 402, 403]
};

Jaiku.iconByName = {
	// Original names
	'car': 301, 'alarmclock': 302, 'loudspeaker': 303, 'tram': 304, 'casette': 305, 'underware': 306, 'rollerblade': 307, 'uzi': 308, 'scoop': 309, 'bomb': 310, 'bra': 311, 'videotape': 312, 'cigarettes': 313, 'vinyl': 314, 'champaign': 315, 'airplain': 316, 'bus': 317, 'grumpy': 318, 'coffee': 319, 'camera': 320, 'basketball': 321, 'beer': 322, 'binoculars': 323, 'boiler': 324, 'walk': 325, 'wallclock': 326, 'trashcan': 327, 'tv': 328, 'computing': 329, 'videocamera': 330, 'game': 331, 'cone': 332, 'driller': 333, 'popcorn': 334, 'playshirt': 335, 'disc': 336, 'event': 337, 'exclamationmark': 338, 'football': 339, 'footballshoe': 340, 'eat': 341, 'gameboy': 342, 'grenade': 343, 'hand': 344, 'hanger': 345, 'hearingprotector': 346, 'love': 347, 'balloons': 348, 'clock': 349, 'barrier': 350, 'laptop': 351, 'megaphone': 352, 'microwave': 353, 'book': 354, 'middlefinger': 355, 'notes': 356, 'question': 357, 'rollator': 358, 'shuttlecock': 359, 'salt': 360, 'scull': 361, 'sk8': 362, 'sleep': 363, 'snorkeling': 364, 'snowflake': 365, 'soda': 366, 'song': 367, 'spraycan': 368, 'sticks': 369, 'storm': 370, 'straitjacket': 371, 'metro': 372, 'luggage': 373, 'sun': 374, 'taxi': 375, 'technics': 376, 'toaster': 377, 'train': 378, 'wheelchair': 379, 'zippo': 380, 'icecream': 381, 'movie': 382, 'makeup': 383, 'bandaid': 384, 'wine': 385, 'clean': 386, 'blading': 387, 'bike': 388, 'pils': 389, 'picnic': 390, 'lifejacket': 391, 'home': 392, 'happy': 393, 'toiletpaper': 394, 'theatre': 395, 'shop': 396, 'search': 397, 'cloudy': 398, 'hurry': 399, 'morning': 400, 'car': 401, 'baby-boy': 402, 'baby-girl': 403,
	// Cleaned names
	airplane: 316, aeroplane: 316, pills: 389, 'walking frame': 358, drill: 333, skull: 361, clock: 326
};

Jaiku.iconById = {
	301: 'car', 302: 'alarmclock', 303: 'loudspeaker', 304: 'tram', 305: 'casette', 306: 'underware', 307: 'rollerblade', 308: 'uzi', 309: 'scoop', 310: 'bomb', 311: 'bra', 312: 'videotape', 313: 'cigarettes', 314: 'vinyl', 315: 'champaign', 316: 'airplain', 317: 'bus', 318: 'grumpy', 319: 'coffee', 320: 'camera', 321: 'basketball', 322: 'beer', 323: 'binoculars', 324: 'boiler', 325: 'walk', 326: 'wallclock', 327: 'trashcan', 328: 'tv', 329: 'computing', 330: 'videocamera', 331: 'game', 332: 'cone', 333: 'driller', 334: 'popcorn', 335: 'playshirt', 336: 'disc', 337: 'event', 338: 'exclamationmark', 339: 'football', 340: 'footballshoe', 341: 'eat', 342: 'gameboy', 343: 'grenade', 344: 'hand', 345: 'hanger', 346: 'hearingprotector', 347: 'love', 348: 'balloons', 349: 'clock', 350: 'barrier', 351: 'laptop', 352: 'megaphone', 353: 'microwave', 354: 'book', 355: 'middlefinger', 356: 'notes', 357: 'question', 358: 'rollator', 359: 'shuttlecock', 360: 'salt', 361: 'scull', 362: 'sk8', 363: 'sleep', 364: 'snorkeling', 365: 'snowflake', 366: 'soda', 367: 'song', 368: 'spraycan', 369: 'sticks', 370: 'storm', 371: 'straitjacket', 372: 'metro', 373: 'luggage', 374: 'sun', 375: 'taxi', 376: 'technics', 377: 'toaster', 378: 'train', 379: 'wheelchair', 380: 'zippo', 381: 'icecream', 382: 'movie', 383: 'makeup', 384: 'bandaid', 385: 'wine', 386: 'clean', 387: 'blading', 388: 'bike', 389: 'pils', 390: 'picnic', 391: 'lifejacket', 392: 'home', 393: 'happy', 394: 'toiletpaper', 395: 'theatre', 396: 'shop', 397: 'search', 398: 'cloudy', 399: 'hurry', 400: 'morning', 401: 'car', 402: 'baby-boy', 403: 'baby-girl'
};

Jaiku.get  = function(){ throw("Developer failed to set their own Jaiku.get function.") };
Jaiku.post = function(){ throw("Developer failed to set their own Jaiku.post function.") };

/* Google Gadget functions
	Jaiku.get = function(url, param, callback){
		_IG_FetchContent(
			url + ((/\?/.test(url)) ? '&' : '?') + param,
			callback
		);
	}
	Jaiku.post = function(url, param, callback){
		_IG_FetchContent(
			url,
			callback,
			{post_data:	param, headers: [['Content-Length', param.length]]}
		);
	}
*/
